﻿using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; 
using RabbitMQ.Client;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Wfxin.RabbitMQ;

namespace Wfxin.Web.Api.MQ
{
    internal interface IOrderConnectionChannelPool : IConnectionChannelPool { }

    internal class RabbitMQOrderConnectionChannelPool : IOrderConnectionChannelPool, IDisposable
    {
        private const int DefaultPoolSize = 50;
        private readonly Func<IConnection> _connectionActivator;
        private readonly ILogger<RabbitMQOrderConnectionChannelPool> _logger;
        private readonly ConcurrentQueue<IModel> _pool;
        private IConnection _connection;

        private int _count;
        private int _maxSize;

        public RabbitMQOrderConnectionChannelPool(ILogger<RabbitMQOrderConnectionChannelPool> logger, IOptions<RabbitMQOptions> options)
        {
            _logger = logger;
            _maxSize = DefaultPoolSize;
            _pool = new ConcurrentQueue<IModel>();

            var p = options.Value;
            //p.ExchangeName = MQ.Constants.EXCHANGE_NAME;
            //p.ExchangeType = MQ.Constants.EXCHANGE_TYPE;
            _connectionActivator = CreateConnection(p);

            var item = p.Items.FirstOrDefault(o => o.Name.Equals("CreateOrder"));

            HostAddress = $"{p.HostName}:{p.Port}";
            Exchange = item.ExchangeName;
            ExchangeType = item.ExchangeType;

            _logger.LogDebug("RabbitMQ configuration of EPS.Order:\r\n {0}", Newtonsoft.Json.JsonConvert.SerializeObject(options,Newtonsoft.Json.Formatting.Indented));
        }

        IModel IConnectionChannelPool.Rent()
        {
            return Rent();
        }

        bool IConnectionChannelPool.Return(IModel connection)
        {
            return Return(connection);
        }

        public string HostAddress { get; }

        public string Exchange { get; }
        public string ExchangeType { get; }

        public IConnection GetConnection()
        {
            if (_connection != null && _connection.IsOpen)
            {
                return _connection;
            }

            _connection = _connectionActivator();
            _connection.ConnectionShutdown += RabbitMQ_ConnectionShutdown;
            return _connection;
        }

        public void Dispose()
        {
            _maxSize = 0;

            while (_pool.TryDequeue(out var context))
            {
                context.Dispose();
            }
        }

        private static Func<IConnection> CreateConnection(RabbitMQOptions options)
        {
            var factory = new ConnectionFactory
            {
                UserName = options.UserName,
                Port = options.Port,
                Password = options.Password,
                VirtualHost = options.VirtualHost,
                RequestedConnectionTimeout = TimeSpan.FromMilliseconds(options.RequestedConnectionTimeout),
                SocketReadTimeout = TimeSpan.FromMilliseconds(options.SocketReadTimeout),
                SocketWriteTimeout = TimeSpan.FromMilliseconds(options.SocketWriteTimeout),
                AutomaticRecoveryEnabled = true,
                TopologyRecoveryEnabled = false
            };

            if (options.HostName.Contains(","))
            {
                return () => factory.CreateConnection(
                    options.HostName.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries));
            }

            factory.HostName = options.HostName;
            return () => factory.CreateConnection();
        }

        private void RabbitMQ_ConnectionShutdown(object sender, ShutdownEventArgs e)
        {
            _logger.LogWarning($"RabbitMQ client connection closed! --> {e.ReplyText}");
        }

        public virtual IModel Rent()
        {
            if (_pool.TryDequeue(out var model))
            {
                Interlocked.Decrement(ref _count);

                Debug.Assert(_count >= 0);

                return model;
            }

            model = GetConnection().CreateModel();

            return model;
        }

        public virtual bool Return(IModel connection)
        {
            if (Interlocked.Increment(ref _count) <= _maxSize)
            {
                _pool.Enqueue(connection);

                return true;
            }

            Interlocked.Decrement(ref _count);

            Debug.Assert(_maxSize == 0 || _pool.Count <= _maxSize);

            return false;
        }
    }
}
